package net.beyondts.mplug;

import net.beyondts.mplug.annotation.Plugin;
import net.beyondts.mplug.utils.AspectUtils;
import net.beyondts.mplug.utils.BeanFactory;
import net.beyondts.mplug.utils.MethodUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * This is a base extension handler for all extension handlers. In the base extension handler, just autowire the PluginManager to all sub-handlers.
 *
 * @author blazecrystal
 */
public abstract class AbstractExtensionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExtensionHandler.class);

    @Autowired
    protected PluginManager pluginManager;

    protected abstract Object handle(ProceedingJoinPoint joinPoint) throws Throwable;

    protected Object invokePluginMethod(JoinPoint joinPoint, String extensionId) throws InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, PluginInvocationException {
        List<Method> pluginMethods = pluginManager.findPluginMethod(extensionId);
        if (pluginMethods == null || pluginMethods.size() < 1) {
            throw new PluginInvocationException("no plugin methods found");
        }
        LOGGER.debug(pluginMethods.size() + " plugin methods for \"" + extensionId + "\" has been found");
        Method pluginMethod = chooseMethod(joinPoint, pluginMethods);
        if (pluginMethod == null) {
            throw new PluginInvocationException("no matched plugin method could be used");
        }
        LOGGER.debug("plugin method with signature \"" + MethodUtils.getSignature(pluginMethod) + "\" matched, and will be invoked");
        Object instance = PluginsUtils.getPluginClassInstance(pluginMethod.getDeclaringClass());
        return pluginMethod.invoke(instance, joinPoint.getArgs());
    }

    protected Object invokeExtensionMethod(ProceedingJoinPoint joinPoint, String extensionId) throws Throwable {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("not an extension or no matched plugin method for \"" + extensionId + "\" found, invoke original method");
        }
        return joinPoint.proceed();
    }

    private Method chooseMethod(JoinPoint joinPoint, List<Method> methods) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        Method chosen = null;
        for (Method method : methods) {
            Plugin plugin = method.getAnnotation(Plugin.class);
            String choose = plugin.choose();
            if (StringUtils.isBlank(choose)) {
                chosen = method;
                continue;
            }
            Object value = findComparedValue(findChooseObject(joinPoint, plugin.choose()), choose);
            if (plugin.compare().equals(value.toString())) {
                chosen = method;
            }
        }
        return chosen;
    }

    private Object findChooseObject(JoinPoint joinPoint, String choose) {
        String name = choose.substring(2);
        int indexOfDot = name.indexOf(".");
        if (indexOfDot > 0) {
            name = name.substring(0, indexOfDot);
        }
        if (choose.startsWith("a:")) {
            return AspectUtils.findArg(joinPoint, name);
        } else {
            return BeanFactory.getBean(name);
        }
    }

    private Object findComparedValue(Object chooseObj, String choose) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Object value = chooseObj;
        int indexOfDoc = choose.indexOf(".");
        if (indexOfDoc < 0) {
            return value;
        }
        String[] parts = choose.substring(indexOfDoc + 1).split("\\.");
        for (String part : parts) {
            part = part.trim();
            if (part.indexOf("()") < 0) {
                value = value.getClass().getField(part).get(value);
            } else {
                value = value.getClass().getMethod(part.substring(0, part.indexOf("("))).invoke(value);
            }
        }
        return value;
    }

    public void setPluginManager(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }
}
